package executor

import (
	"context"
	"encoding/binary"
	"errors"
	"math/big"

	"github.com/adwindone/sdk-go/crypto"
	"github.com/adwindone/sdk-go/model/blockchain"
	"github.com/adwindone/sdk-go/model/common"
	"github.com/adwindone/sdk-go/model/evm"
	"github.com/adwindone/sdk-go/model/executor"
	"github.com/adwindone/sdk-go/model/request"
	"github.com/adwindone/sdk-go/model/response"
	"github.com/adwindone/sdk-go/util/hexutil"

	"github.com/google/uuid"
	"go.uber.org/zap"
	"google.golang.org/protobuf/proto"
)

// GetAbi returns the contract ABI information for the given address.
func (pc *peopleChainExecutorManage) GetAbi(ctx context.Context, address string) (string, error) {
	addr, err := hexutil.Decode(address)
	if err != nil {
		pc.log.Error("GetAbi", zap.String("msg", "address decode failed"), zap.Error(err))
		return "", err
	}
	abiInfo, err := pc.evmService.GetAbi(ctx, &common.Address{Address: addr})
	if err != nil {
		pc.log.Error("GetAbi", zap.String("msg", "get abi failed"), zap.Error(err))
		return "", err
	}
	return string(abiInfo.BytesAbi), nil
}

// GetCode returns the contract bytecode for the given contract address.
func (pc *peopleChainExecutorManage) GetCode(ctx context.Context, address string) (string, error) {
	addr, err := hexutil.Decode(address)
	if err != nil {
		pc.log.Error("GetCode", zap.String("msg", "address decode failed"), zap.Error(err))
		return "", err
	}
	abiInfo, err := pc.evmService.GetCode(ctx, &common.Address{Address: addr})
	if err != nil {
		pc.log.Error("GetCode", zap.String("msg", "get code failed"), zap.Error(err))
		return "", err
	}
	if abiInfo == nil {
		pc.log.Error("GetCode", zap.String("msg", "code info is null"))
		return "", errors.New("code info is null")
	}
	return hexutil.Encode(abiInfo.ByteCode), nil
}

// GetTransactionReceipt returns the transaction receipt for the given transaction hash.
func (pc *peopleChainExecutorManage) GetTransactionReceipt(ctx context.Context, txHash string) (*response.Receipt, error) {
	hash, err := hexutil.Decode(txHash)
	if err != nil {
		return nil, err
	}
	info := new(common.Hash)
	info.Hash = hash
	receiptInfo, err := pc.evmService.GetTransactionReceipt(ctx, info)
	if err != nil {
		pc.log.Error("GetTransactionReceipt", zap.Error(err))
		return nil, err
	}
	if receiptInfo == nil {
		pc.log.Error("GetTransactionReceipt", zap.String("msg", "transaction receipt info is null"))
		return nil, errors.New("transaction receipt info is null")
	}
	newReceiptInfo := response.ReceiptConvert(receiptInfo)
	return newReceiptInfo, nil
}

// GetBalance returns the account balance for the given address.
func (pc *peopleChainExecutorManage) GetBalance(ctx context.Context, address string) (*big.Int, error) {
	addr, err := hexutil.Decode(address)
	if err != nil {
		pc.log.Error("GetBalance", zap.String("msg", "address decode failed"), zap.Error(err))
		return nil, err
	}
	balanceInfo, err := pc.evmService.GetBalance(ctx, &common.Address{Address: addr})
	if err != nil {
		pc.log.Error("GetBalance", zap.String("msg", "get code failed"), zap.Error(err))
		return nil, err
	}
	balance, err := hexutil.DecodeBig(hexutil.Encode(balanceInfo.Value))
	if err != nil {
		return nil, err
	}
	return balance, nil
}

// GetTransactionCount returns the number of transactions sent from the account address.
func (pc *peopleChainExecutorManage) GetTransactionCount(ctx context.Context, address string) (*big.Int, error) {
	addr, err := hexutil.Decode(address)
	if err != nil {
		pc.log.Error("GetBalance", zap.String("msg", "address decode failed"), zap.Error(err))
		return nil, err
	}
	nonceInfo, err := pc.evmService.GetTransactionCount(ctx, &common.Address{Address: addr})
	if err != nil {
		pc.log.Error("GetBalance", zap.String("msg", "get code failed"), zap.Error(err))
		return nil, err
	}
	if nonceInfo == nil {
		pc.log.Error("GetBalance", zap.String("msg", "nonce info is null"))
		return nil, errors.New("nonce info is null")
	}
	bigInt := new(big.Int)
	bigInt.SetBytes(nonceInfo.Nonce)
	return bigInt, nil
}

// EstimateQuota estimates the quota consumed by executing a transaction.
func (pc *peopleChainExecutorManage) EstimateQuota(ctx context.Context, crt *request.CallTransaction) (*big.Int, error) {
	from, err := hexutil.Decode(crt.From)
	if err != nil {
		pc.log.Error("from address decode", zap.Error(err))
		return nil, err
	}
	to, err := hexutil.Decode(crt.To)
	if err != nil {
		pc.log.Error("to address decode", zap.Error(err))
		return nil, err
	}
	info := new(executor.CallRequest)
	info.From = from
	info.To = to
	info.Method = crt.Method
	info.Args = crt.Args
	info.Height = crt.Height
	quotaInfo, err := pc.evmService.EstimateQuota(ctx, info)
	if err != nil {
		pc.log.Error("EstimateQuota", zap.Error(err))
		return nil, err
	}
	if quotaInfo == nil {
		pc.log.Error("EstimateQuota", zap.String("msg", "quota info is null"))
		return nil, errors.New("quota info is null")
	}
	bigInt := new(big.Int)
	bigInt.SetBytes(quotaInfo.BytesQuota)
	return bigInt, nil
}

// GetReceiptProof returns the ReceiptProof information of the transaction by transaction hash.
func (pc *peopleChainExecutorManage) GetReceiptProof(ctx context.Context, txHash string) (*evm.ReceiptProof, error) {
	hash, err := hexutil.Decode(txHash)
	if err != nil {
		return nil, err
	}
	info := new(common.Hash)
	info.Hash = hash
	receiptProofInfo, err := pc.evmService.GetReceiptProof(ctx, info)
	if err != nil {
		pc.log.Error("GetReceiptProof", zap.Error(err))
		return nil, err
	}
	return receiptProofInfo, nil
}

// GetRootsInfo returns the RootsInfo for the given block height.
func (pc *peopleChainExecutorManage) GetRootsInfo(ctx context.Context, blockNumber uint64) (*evm.RootsInfo, error) {
	info := new(evm.BlockNumber)
	info.BlockNumber = blockNumber
	rootsInfo, err := pc.evmService.GetRootsInfo(ctx, info)
	if err != nil {
		pc.log.Error("GetRootsInfo", zap.Error(err))
		return nil, err
	}
	return rootsInfo, nil
}

// Call returns the chain data for the given call transaction.
func (pc *peopleChainExecutorManage) Call(ctx context.Context, crt *request.CallTransaction) ([]byte, error) {
	from, err := hexutil.Decode(crt.From)
	if err != nil {
		pc.log.Error("from address decode", zap.Error(err))
		return nil, err
	}

	to, err := hexutil.Decode(crt.To)
	if err != nil {
		pc.log.Error("to address decode", zap.Error(err))
		return nil, err
	}

	info := new(executor.CallRequest)
	info.From = from
	info.To = to
	info.Method = crt.Method
	info.Args = crt.Args
	info.Height = crt.Height

	callResponse, err := pc.executorService.Call(ctx, info)
	if err != nil {
		pc.log.Error("call", zap.Error(err))
		return nil, err
	}

	if callResponse == nil {
		pc.log.Error("call", zap.String("msg", "call response is null"))
		return nil, errors.New("call response is null")
	}
	return callResponse.Value, nil
}

// Exec executes the given transactions and returns the hash response.
func (pc *peopleChainExecutorManage) Exec(ctx context.Context, rtxs []*request.Transaction, header *blockchain.BlockHeader, blockNumber uint64, version uint32, proof []byte, stateRoot []byte) (*common.HashResponse, error) {
	normalTxs := make([]*blockchain.RawTransaction, 0)
	for _, rtx := range rtxs {
		// Transaction structure
		tx := new(blockchain.Transaction)
		// nonce Random number used to prevent replay attacks
		tx.Nonce = uuid.NewString()
		// to Receiver address
		tx.To = []byte(rtx.To)
		if len(rtx.To) > 0 {
			to, err := hexutil.Decode(rtx.To)
			if err != nil {
				pc.log.Error("to address decode", zap.Error(err))
				return nil, err
			}
			tx.To = to
		}
		// data Transaction data
		data, err := hexutil.Decode(rtx.Data)
		if err != nil {
			pc.log.Error("contract code decode", zap.Error(err))
			return nil, err
		}
		tx.Data = data
		// quota User pays the miner's fee
		tx.Quota = rtx.Quota
		// version Contract version information
		tx.Version = rtx.Version
		// chainId ChainId
		chainId, err := hexutil.Decode(rtx.ChainId)
		if err != nil {
			pc.log.Error("chainId decode", zap.Error(err))
			return nil, err
		}
		tx.ChainId = chainId
		// value The number of native tokens in the transaction
		valueBytes := make([]byte, 32)
		binary.BigEndian.PutUint32(valueBytes, rtx.Value)
		tx.Value = valueBytes
		// validUntilBlock Timeout mechanism, cancel transaction if exceeds set block height
		tx.ValidUntilBlock = blockNumber + 99
		// Transaction hash
		byteData, err := proto.Marshal(tx)
		if err != nil {
			pc.log.Error("tx proto marshal", zap.Error(err))
			return nil, err
		}
		txHash := crypto.Sm3Hash(byteData)
		// Sender address
		sender, err := hexutil.Decode(rtx.SenderAddress)
		if err != nil {
			pc.log.Error("sender address decode", zap.Error(err))
			return nil, err
		}
		// Signature
		signature, err := crypto.Signature(rtx.SenderPrivateKey, txHash)
		if err != nil {
			pc.log.Error("tx signature", zap.Error(err))
			return nil, err
		}
		// Transaction receipt
		witness := new(blockchain.Witness)
		witness.Sender = sender
		witness.Signature = signature
		// Transaction structure
		utx := new(blockchain.UnverifiedTransaction)
		utx.Transaction = tx
		utx.TransactionHash = txHash
		utx.Witness = witness
		normalTxs = append(normalTxs, &blockchain.RawTransaction{Tx: &blockchain.RawTransaction_NormalTx{NormalTx: utx}})
	}
	info := new(blockchain.Block)
	info.Header = header
	info.Body = &blockchain.RawTransactions{Body: normalTxs}
	info.Proof = proof
	info.StateRoot = stateRoot
	info.Version = version
	hashResponse, err := pc.executorService.Exec(ctx, info)
	if err != nil {
		pc.log.Error("exec", zap.Error(err))
		return nil, err
	}
	return hashResponse, nil
}
